package chapter02.part03;

import chapter02.FileUtil;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.*;

/**
 * 修改默认的序列化机制
 * 
 * @author insight
 * @since 2021/8/16
 */
public class CustomSerializable {
    public static void main(String[] args) throws IOException,
            ClassNotFoundException {
        ObjectOutputStream out =
                new ObjectOutputStream(FileUtil.getTestFileOutputStreamInstance());
        ObjectInputStream in =
                new ObjectInputStream(FileUtil.getTestFileInputStreamInstance());
        // 需求/场景: 部分数据域不需要序列化
        
        // 方案1: 使用 transient 修饰 (实现 Serializable 接口的情况下)
        transientTest(out, in);

        // 方案2: 静态变量不可序列化 (不修改序列化方法的情况下)
        staticTest(out, in);
        
        // 方案3: 添加自定义方法 
        customTest(out, in);
        
        // 方案4: 类似方案3 实现 Externalizable 接口 自定义序列化过程
        // 3 和 4 的区别在于: 3 不关心超类和其他类 4 包括超类数据 (不是很懂)
        // 另外 4 在读取时 会调用无参构造器 因此必须要有 public 无参构造函数
        // 3 的方法是私有的 4 是公有的 因此 readExternal 有修改现有对象的风险
        externalTest(out, in);
    }

    private static void externalTest(ObjectOutputStream oos, ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ExternalTest djik = new ExternalTest("djik", "god");
        System.out.println("Origin: " + djik);
        oos.writeObject(djik);
        ExternalTest newDjik = (ExternalTest) ois.readObject();
        System.out.println("readObject: " + newDjik);
        // 风险
        djik.readExternal(ois);
        System.out.println("External Risk: " + djik);
    }

    private static void customTest(ObjectOutputStream oos, ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 自定义规定格式的方法 不再自动序列化 而是调用新方法 (注意不是 override)
        // private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
        // private void writeObject(ObjectOutputStream out) throws IOException
        CustomTest fuu = new CustomTest("fuu", "sumurai");
        // 具体原理好像是反射
        System.out.println("Origin: " + fuu);
        oos.writeObject(fuu);
        // 修改无效
        CustomTest.home = "JJJAP";
        CustomTest newFuu = (CustomTest) ois.readObject();
        // transient static 修饰的字段 依旧可以序列化
        // 因为自定义了序列化机制
        System.out.println("readObject: " + newFuu);
    }

    private static void staticTest(ObjectOutputStream oos, ObjectInputStream ois) throws IOException, ClassNotFoundException {
        Test jin = new Test("jin", "jap");
        System.out.println("Origin: " + jin);
        oos.writeObject(jin);
        // 写入之后修改 static 字段
        Test.home = "changed";
        Test newJin = (Test) ois.readObject();
        // ret: changed
        System.out.println("readObject: " + newJin);
    }

    private static void transientTest(ObjectOutputStream oos, ObjectInputStream ois) throws IOException, ClassNotFoundException {
        Test mugen = new Test("mugen", "Ryukyu");
        System.out.println("Origin: " + mugen);
        oos.writeObject(mugen);
        Test newMugen = (Test) ois.readObject();
        // ret: null
        System.out.println("readObject: " + newMugen);
    }

    @AllArgsConstructor
    private static class Test implements Serializable {
        private String name;
        private transient String info;
        public static String home = "test";

        @Override
        public String toString() {
            return "Test{" +
                    "name='" + name + '\'' +
                    ", info='" + info + '\'' +
                    ", home='" + home + '\'' +
                    '}';
        }
    }

    @AllArgsConstructor
    private static class CustomTest implements Serializable {
        private String name;
        private transient String info;
        private static String home = "test";

        /**
         * 自定义序列化字段
         */
        private void writeObject(ObjectOutputStream out) throws IOException {
            // 只能在此方法调用
            out.defaultWriteObject();
            out.writeObject(info);
            out.writeObject(home);
        }

        private void readObject(ObjectInputStream in) throws IOException,
                ClassNotFoundException {
            // 同理
            in.defaultReadObject();
            info = ((String) in.readObject());
            home = ((String) in.readObject());
        }

        @Override
        public String toString() {
            return "CustomTest{" +
                    "name='" + name + '\'' +
                    ", info='" + info + '\'' +
                    ", home='" + home + '\'' +
                    '}';
        }
    }

    @AllArgsConstructor
    @NoArgsConstructor
    private static class ExternalTest implements Externalizable {
        private String name;
        private String info;

        @Override
        public String toString() {
            return "ExternalTest{" +
                    "name='" + name + '\'' +
                    ", info='" + info + '\'' +
                    '}';
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
            out.writeObject("This is info");
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException,
                ClassNotFoundException {
            name = "null";
            info = "null";
        }
    }
}